﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Green.Utility;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.ComponentModel;

namespace Green.ObjectPickUper
{
    public class DefaultObjectPickUper : ObjectPickUperBase
    {
        //|| t.GetGenericTypeDefinition() == typeof(Nullable<>)
        public static Predicate<Type> IsPrimitivePredicate = (t => t.IsPrimitive || t == typeof(string) || t.IsEnum || t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>) || DefaultObjectPickUper.IsPrimitivePredicate(t.GetGenericArguments()[0]))));

        private const string PropertyChangedMethod = "OnNotifyPropertyChanged";
        public override string PickUp(Type pickedType, string suffix)
        {
            CodeNamespace nameSpace = GeneratorCodeNameSpace(pickedType, suffix);
            return GeneratorCode(nameSpace, "c#");
        }

        protected virtual string GeneratorCode(CodeNamespace nameSpace, string language)
        {
            //CodeGeneratorOptions       
            CodeGeneratorOptions geneOptions = new CodeGeneratorOptions();//代码生成选项      
            geneOptions.BlankLinesBetweenMembers = false;
            geneOptions.BracingStyle = "C";
            geneOptions.ElseOnClosing = true;
            geneOptions.IndentString = "    ";
            //TextWrite    
            StringBuilder sb = new StringBuilder();
            System.IO.StringWriter sw = new System.IO.StringWriter(sb);
            CodeDomProvider.CreateProvider(language).GenerateCodeFromNamespace(nameSpace, sw, geneOptions);
            sw.Close();
            return sb.ToString();
        }

        protected virtual CodeNamespace GeneratorCodeNameSpace(Type pickedType, string suffix)
        {
            CodeNamespace nameSpace = new CodeNamespace(pickedType.Namespace);
            NamespaceImports(nameSpace);

            var pickClass = InitPickClass(pickedType, suffix);
            pickClass.Attributes = MemberAttributes.Public;
            nameSpace.Types.Add(pickClass);

            var pickPropertys = pickedType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).Where(p => p.GetAttribte<IgnorePickUpAttribute>(true) == null);

            //加标签的特殊类型
            var specialPickUpPropertys = pickPropertys.Where(t => t.GetAttribte<PickUpBaseAttribute>(true) != null);
            //未加标签
            var nonSpecialPickPropertys = pickPropertys.Except(specialPickUpPropertys);
            //未标签的基础类型
            var primitivePropertys = nonSpecialPickPropertys.Where(t => IsPrimitivePredicate(t.PropertyType));

            primitivePropertys.ToList().ForEach(p =>
            {
                GeneratorPrimitiveProperty(pickClass, p);
            });

            //未加标签的非基础类型
            var nonSpecialNotPrimitivePickPropertys = nonSpecialPickPropertys.Except(primitivePropertys);

            nonSpecialNotPrimitivePickPropertys.ToList().ForEach(p =>
            {
                GenerObjectPickUperAttribute(suffix, pickClass, p);
            });

            specialPickUpPropertys.ToList().ForEach(p =>
            {
                var attr = p.GetAttribte<PickUpBaseAttribute>(true);
                if (attr is ObjectPickUperAttribute)
                {
                    GenerObjectPickUperAttribute(suffix, pickClass, p);
                }
                else if (attr is CollectionPickUperAttribute)
                {
                    GenerCollectionPickUperAttribute(suffix, pickClass, p, attr as CollectionPickUperAttribute);
                }
            });

            ImplementICloneable(pickClass, suffix);
            return nameSpace;
        }

        protected virtual void ImplementICloneable(CodeTypeDeclaration pickClass, string suffix)
        {
            var method = new CodeMemberMethod();
            method.Name = "Clone";
            method.ReturnType = new CodeTypeReference(typeof(object));
            method.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            List<CodeStatement> statements = new List<CodeStatement>();
            var cloneObj = new CodeVariableDeclarationStatement(pickClass.Name, "cloneObj");
            statements.Add(cloneObj);
            statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(cloneObj.Name), new CodeObjectCreateExpression(pickClass.Name)));
            var iterationVar = new CodeVariableDeclarationStatement(typeof(int).FullName, "i");
            pickClass.Members.OfType<CodeTypeMember>().ToList()
                .ForEach(t =>
                {
                    var propert = t as CodeMemberProperty;
                    if (propert != null)
                    {
                        var type = (Type)propert.UserData["Type"];
                        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>) || type.GetInterface(typeof(IList<>).FullName) != null)
                        {
                            var list = new CodeVariableDeclarationStatement(propert.Type, propert.Name + "List");
                            statements.Add(list);
                            statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(list.Name), new CodeObjectCreateExpression(propert.Type)));
                            var foreachStatement = new CodeIterationStatement(new CodeAssignStatement(new CodeVariableReferenceExpression("i"), new CodePrimitiveExpression(0)),
                                new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("i"), CodeBinaryOperatorType.LessThan, new CodePropertyReferenceExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propert.Name), "Count")),
                                new CodeAssignStatement(new CodeVariableReferenceExpression("i"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("i"), CodeBinaryOperatorType.Add, new CodePrimitiveExpression(1)))
                               );
                            CodeExpression rightExpress = new CodeArrayIndexerExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propert.Name), new CodeVariableReferenceExpression("i"));
                            var genType = type.GetGenericArguments()[0];
                            if (!IsPrimitivePredicate(genType))
                            {
                                rightExpress = new CodeMethodInvokeExpression(rightExpress, "Clone");
                            }
                            if (IsPrimitivePredicate(genType))
                            {
                                foreachStatement.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(list.Name), "Add", rightExpress));
                            }
                            else
                            {
                                foreachStatement.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(list.Name), "Add", new CodeCastExpression((genType.FullName + suffix), rightExpress)));
                            }
                            statements.Add(new CodeConditionStatement(new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propert.Name), CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)),
                                 iterationVar,
                                foreachStatement));

                            statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeVariableReferenceExpression(cloneObj.Name), "_"+propert.Name), new CodeVariableReferenceExpression(list.Name)));
                        }
                        else if (IsPrimitivePredicate(type))
                        {
                            statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeVariableReferenceExpression(cloneObj.Name), "_" + propert.Name),
                                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propert.Name)));
                        }

                        else
                        {
                            statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeVariableReferenceExpression(cloneObj.Name), "_" + propert.Name),
                               new CodeCastExpression(type.FullName + suffix, new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propert.Name), "Clone"))));
                        }
                    }
                });

            statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression(cloneObj.Name)));
            method.Statements.AddRange(statements.ToArray());
            pickClass.Members.Add(method);
        }

        protected virtual void GeneratorPrimitiveProperty(CodeTypeDeclaration pickClass, System.Reflection.PropertyInfo p)
        {
            CodeMemberField filed;
            CodeMemberProperty property;
            GeneratorProperty(p, p.Name, string.Empty, out filed, out property);
            if (p.PropertyType.IsGenericType && (p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) || p.PropertyType.GetInterface(typeof(IList<>).FullName) != null))
            {
                property.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Green.AgileMapper.CollectionMappingAttribute"),
                     new CodeAttributeArgument("Name", new CodePrimitiveExpression(p.Name)),
                     new CodeAttributeArgument("IsConvertTo", new CodePrimitiveExpression(true)),
                     new CodeAttributeArgument("IsConvertFrom", new CodePrimitiveExpression(true)),
                     new CodeAttributeArgument("IsDeleteNotInFromItem", new CodePrimitiveExpression(true))
                     ));
            }

            if (property != null)
            {
                pickClass.Members.AddRange(new CodeTypeMember[] { filed, property });
            }
        }

        protected virtual void GenerCollectionPickUperAttribute(string suffix, CodeTypeDeclaration pickClass, System.Reflection.PropertyInfo p, CollectionPickUperAttribute collectionMapping)
        {
            if (p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) || p.PropertyType.GetInterface(typeof(IList<>).FullName) != null)
            {
                CodeMemberField filed = null;
                CodeMemberProperty property = null;
                GeneratorProperty(p, p.Name, suffix, out filed, out property);
                property.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Green.AgileMapper.CollectionMappingAttribute"),
                          new CodeAttributeArgument("Name", new CodePrimitiveExpression(p.Name)),
                          new CodeAttributeArgument("IsConvertTo", new CodePrimitiveExpression(collectionMapping.IsConvertTo)),
                          new CodeAttributeArgument("IsConvertFrom", new CodePrimitiveExpression(collectionMapping.IsConvertFrom)),
                          new CodeAttributeArgument("IsDeleteNotInFromItem", new CodePrimitiveExpression(collectionMapping.IsDeleteNotInFromItem)),
                          new CodeAttributeArgument("EqualExpression", new CodePrimitiveExpression(collectionMapping.EqualExpression))
                          ));
                if (property != null)
                {
                    pickClass.Members.AddRange(new CodeTypeMember[] { filed, property });
                }
            }
        }

        protected virtual void GenerObjectPickUperAttribute(string suffix, CodeTypeDeclaration pickClass, System.Reflection.PropertyInfo p)
        {

            if (p.PropertyType.GetInterface(typeof(IList<>).FullName) == null && p.PropertyType.IsClass)//Object
            {
                CodeMemberField filed = null;
                CodeMemberProperty property = null;
                var objectMapping = p.GetAttribte<ObjectPickUperAttribute>(true);
                if (objectMapping != null && !objectMapping.IsPlanePick)
                {
                    GeneratorProperty(p, objectMapping.Prefix + p.Name, suffix, out filed, out property);
                    property.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Green.AgileMapper.ObjectMappingAttribute"),
                        new CodeAttributeArgument("Name", new CodePrimitiveExpression(p.Name))
                     ));
                    if (property != null)
                    {
                        pickClass.Members.AddRange(new CodeTypeMember[] { filed, property });
                    }
                }
                if (property == null)
                {
                    var pickPropertyAll = p.PropertyType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).Where(k => k.GetAttribte<IgnorePickUpAttribute>(true) == null && IsPrimitivePredicate(k.PropertyType));
                    //平面抽取

                    pickPropertyAll.ToList().ForEach(k =>
                    {
                        var propertyNamePrefix = objectMapping == null || string.IsNullOrEmpty(objectMapping.Prefix) ? p.Name : objectMapping.Prefix;
                        GeneratorProperty(k, propertyNamePrefix + k.Name, suffix, out filed, out property);
                        property.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Green.AgileMapper.MappingAttribute"),
                              new CodeAttributeArgument("Name", new CodePrimitiveExpression(p.Name + "." + k.Name)),
                              new CodeAttributeArgument("IsConvertTo", new CodePrimitiveExpression(true)),
                              new CodeAttributeArgument("IsConvertFrom", new CodePrimitiveExpression(true))));
                        if (property != null)
                        {
                            pickClass.Members.AddRange(new CodeTypeMember[] { filed, property });
                        }
                    });
                }
            }
        }

        protected virtual void NamespaceImports(CodeNamespace nameSpace)
        {
            nameSpace.Imports.Add(new CodeNamespaceImport("System"));
            nameSpace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
            nameSpace.Imports.Add(new CodeNamespaceImport("System.Linq"));
            nameSpace.Imports.Add(new CodeNamespaceImport("System.Text"));
        }

        protected virtual CodeTypeDeclaration InitPickClass(Type pickedType, string suffix)
        {
            var pickName = string.Format("{0}{1}", pickedType.Name, suffix);
            var pickClass = new CodeTypeDeclaration(pickName);
            if (BaseType != null)
            {
                pickClass.BaseTypes.Add(new CodeTypeReference(BaseType));
            }
            if (IsImplementINotifyPropertyChanged && (BaseType == null || BaseType.GetInterface(typeof(INotifyPropertyChanged).FullName) == null))
            {
                pickClass.BaseTypes.Add(new CodeTypeReference(typeof(INotifyPropertyChanged)));
                var notifyEvent = new CodeMemberEvent() { Attributes = MemberAttributes.Public | MemberAttributes.Final };
                notifyEvent.Name = "PropertyChanged";
                notifyEvent.Type = new CodeTypeReference(typeof(PropertyChangedEventHandler));
                pickClass.Members.Add(notifyEvent);
            }

            if (BaseType == null || BaseType.GetMethod(PropertyChangedMethod, new Type[] { typeof(string) }) == null)
            {
                var methodNotify = new CodeMemberMethod();
                methodNotify.Name = PropertyChangedMethod;
                methodNotify.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                methodNotify.Parameters.AddRange(new CodeParameterDeclarationExpression[] 
                {
                    new CodeParameterDeclarationExpression(typeof(string),"property"),                   
                });

                var propertyChangeEvent = new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged");
                var conditionStatement = new CodeConditionStatement(new CodeBinaryOperatorExpression(propertyChangeEvent, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)),
                new CodeExpressionStatement(new CodeDelegateInvokeExpression(
                           propertyChangeEvent, new CodeThisReferenceExpression(),
                           new CodeObjectCreateExpression(new CodeTypeReference(typeof(PropertyChangedEventArgs)), new CodeVariableReferenceExpression("property"))
                           ))
                    );
                methodNotify.Statements.Add(conditionStatement);
                pickClass.Members.Add(methodNotify);
            }

            pickClass.BaseTypes.Add(new CodeTypeReference(typeof(ICloneable)));
            if (IsSOAObject)
            {
                pickClass.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("System.Runtime.Serialization.DataContractAttribute")));
            }
            pickClass.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(SerializableAttribute))));

            return pickClass;
        }

        protected virtual void GeneratorProperty(System.Reflection.PropertyInfo p, string name, string typeSuffix, out CodeMemberField filed, out CodeMemberProperty property)
        {
            if (BaseType != null && BaseType.GetProperty(name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) != null)
            {
                filed = null;
                property = null;
                return;
            }
            string typeName = IsPrimitivePredicate(p.PropertyType) ? p.PropertyType.FullName : (p.PropertyType.FullName + typeSuffix);
            if (p.PropertyType.IsGenericType)
            {
                if (p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) || p.PropertyType.GetInterface(typeof(IList<>).FullName) != null)
                {
                    if (IsPrimitivePredicate(p.PropertyType))
                    {
                        typeName = string.Format("System.Collections.Generic.List<{0}>", p.PropertyType.GetGenericArguments()[0].FullName);
                    }
                    else
                    {
                        typeName = string.Format("System.Collections.Generic.List<{0}>", p.PropertyType.GetGenericArguments()[0].FullName + typeSuffix);
                    }
                }
            }

            filed = new CodeMemberField();
            filed.Name = "_" + name;
            filed.Type = new CodeTypeReference(typeName);
            filed.Attributes = MemberAttributes.Private | MemberAttributes.Final;

            property = new CodeMemberProperty();
            property.Name = name;
            property.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            property.Type = new CodeTypeReference(typeName);
            property.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), filed.Name)));

            //SetFunction;
            //property.SetStatements.Add();
            CodeStatement setFunction = null;
            setFunction = new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), filed.Name), new CodePropertySetValueReferenceExpression());

            if (IsImplementINotifyPropertyChanged)
            {
                var conditionStatement = new CodeConditionStatement(new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), filed.Name), CodeBinaryOperatorType.IdentityInequality, new CodePropertySetValueReferenceExpression()));
                conditionStatement.TrueStatements.Add(setFunction);
                conditionStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), PropertyChangedMethod, new CodePrimitiveExpression(property.Name)));
                setFunction = conditionStatement;
            }

            property.UserData.Add("Type", p.PropertyType);
            property.SetStatements.Add(setFunction);

            if (IsSOAObject)
            {
                property.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("System.Runtime.Serialization.DataMemberAttribute")));
            }
        }
    }
}
